﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Channels;
using System.ServiceModel;
using HomeFramework.Core;
using System.IO;
using System.Xml;
using System.ServiceModel.Description;
using System.Diagnostics;
using HomeFramework.Model;
using System.Reflection;

namespace HomeFramework.Services
{
    partial class HomeFrameworkEngine
    {
        [Serializable]
        internal struct GeneratorMessageRequest
        {
            public static readonly object LoadingFlag = new object();
            public byte[] messageBytes;
            public string mesageVersion;
            public string ContractFullName;
            public string HandlerFullName;
            public string bindingType;
            public string assemblyName;
            public byte[][] references;
            public byte[] assembly;
        }

        [Serializable]
        internal struct GeneratorMessageResponse
        {
            public byte[] Bytes;
        }

        #region CONTRACTS

        [ServiceContract(Name = "IHomeFrameworkService")]
        private interface IMessageProcessor
        {
            [OperationContract(ReplyAction = "*", Action = "*")]
            Message ProcessMessage(ProcessMessageRequest request);
        }

        private class MessageProcessor : IGenericHandler
        {
            public Message Invoke(Message request) { return request; }
        }

        [ServiceContract(Name = "IHomeFrameworkService")]
        private interface IMessageResponseProcessor
        {
            [OperationContract(ReplyAction = "*", Action = "*")]
            ProcessMessageResponse ProcessMessage(Message request);
        }

        #endregion

        internal class MessageGenerator
        {
            public static Message ProcessRequest(string name, string version, Message request, string bindingType)
            {
                var binding = NewTempBinding(bindingType);
                var path = RandomPath(binding);

                using (var h = new ServiceHost(typeof(MessageProcessor)))
                {
                    h.AddServiceEndpoint(typeof(IGenericHandler), binding, path);
                    h.Open();
                    using (var f = new ChannelFactory<IMessageProcessor>(binding, path))
                    {
                        var c = f.CreateChannel();
                        var risp = c.ProcessMessage(new ProcessMessageRequest()
                        {
                            messageBody = MessageXmlByteWriter.MessageToBytes(request, request.Version),
                            messageVersion = MessageVersionToString(request.Version),
                            serviceName = name,
                            serviceVersion = version,
                            bindingType = bindingType
                        });
                        return risp;
                    }
                }
            }

            public static Message ProcessResponse(Message request, string bindingType)
            {
                var bf = request.CreateBufferedCopy(int.MaxValue);

                try
                {
                    var bd = bf.CreateMessage().GetBody<ProcessMessageResponse>();
                    return MessageXmlByteWriter.MessageFromBytes(bd.messageBody, request.Version);
                }
                catch (Exception)
                {
                    return bf.CreateMessage();
                }
            }

            private readonly static TNX.Common.CacheManager ProcessorCache = new TNX.Common.CacheManager();
            public static byte[] ProcessMessage(Handler h, byte[] bytes, string version, string bindingType)
            {
                var context = ProcessorCache.GetOrDrillShortSliding(() =>
                    {
                        return new TNX.Common.Legacy.AppDomainContextSmartCached<GeneratorMessageRequest, GeneratorMessageResponse>();
                    }, h.AssemblyFullName);

                var rq = new GeneratorMessageRequest
                {
                    messageBytes = bytes,
                    mesageVersion = version,
                    bindingType = bindingType,
                    HandlerFullName = h.HandlerFullName,
                    ContractFullName = h.ContractFullName,
                    assemblyName = h.AssemblyFullName,
                    references = h.References.Select(x => x.ReferenceAssembly).ToArray(),
                    assembly = h.AssemblyData,
                };

                var response = context.ExecuteCrossDomainOperation(request =>
                    {
                        Assembly assembly;
                        lock (GeneratorMessageRequest.LoadingFlag)
                        {
                            assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName == request.assemblyName);
                            if (assembly == null)
                            {
                                foreach (var b in request.references)
                                    Assembly.Load(b);

                                assembly = Assembly.Load(request.assembly);
                            }
                        }

                        var binding = NewTempBinding(request.bindingType);
                        var tempPath = RandomPath(binding);

                        var handler = assembly.GetType(request.HandlerFullName);
                        var contract = assembly.GetType(request.ContractFullName);
                        try
                        {
                            var message = MessageXmlByteWriter.MessageFromBytes(request.messageBytes, MessageVersionFromString(request.mesageVersion));
                            using (var host = new ServiceHost(handler))
                            {
                                host.Faulted += new EventHandler(host_Faulted);
                                host.AddServiceEndpoint(contract, binding, tempPath);
                                (host.Description.Behaviors.Single(x => x is ServiceDebugBehavior) as ServiceDebugBehavior).IncludeExceptionDetailInFaults = true;
                                host.Open();
                                using (var f = new ChannelFactory<IGenericHandler>(binding, tempPath))
                                {
                                    var c = f.CreateChannel();
                                    var risp = c.Invoke(message);
                                    Log.WriteLine(Log.SourceEnum.HomeFrameworkEngine, TraceEventType.Verbose, "Responding for action {0}", risp.Headers.Action);
                                    return new GeneratorMessageResponse { Bytes = MessageXmlByteWriter.MessageToBytes(risp, risp.Version) };
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            var exception = string.Format("Unable to process the message!\r\n{0}\r\n{1}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "");

                            Log.WriteLine(Log.SourceEnum.HomeFrameworkEngine, TraceEventType.Warning, exception);
                            throw new FaultException(exception);
                        }
                    }, rq);

                return response.Bytes;

            }
        }

        private static void host_Faulted(object sender, EventArgs e)
        {
            Debug.WriteLine("host_Faulted: " + e.ToString());
        }
    }
}